function OrgsTreeController(orgs) {
    this.orgs = orgs;
    this.treeVersion = 0;

    var orgsTreeControllerThis = this;
    
    $(document).bind('orgsDisable', function(e) {
        $("#tree").dynatree("disable");
    });
    $(document).bind('orgsEnable', function(e) {
        $("#tree").dynatree("enable");
    });
    
    // this is invoked as the user types into the name input element
    $(document).bind('orgsTreeController.nodeTitleChange', function(e, title, key) {
        if(key){
            // This is for the case of an unsaved change, for a node that is no longer active
            // because the user clicked on another one.
            var node = orgsTreeControllerThis.getNode(key);
            node.setTitle(title);
        }else{
            var node = $("#tree").dynatree("getTree").getActiveNode();
            node.setTitle(title);
        };
    });
    
    $(document).bind('orgsTreeController.nodeDataChange', function(e, title, key, version) {
            var node = orgsTreeControllerThis.getNode(key);
            node.setTitle(title);
            var targetOrg = orgsTreeControllerThis.getOrg(key);
            targetOrg.title = title;
            targetOrg.version = version;
    });
    
    
    var onDelete = function(parentNode){
        $(document).trigger('orgsEnable');
        if(parentNode == null || parentNode.getLevel() == 0){
            homeController.orgsClickHandler();
        }else{
            orgsTreeControllerThis.orgClickHandler(parentNode, true);
        }
    }

    var getUniqueName = function(orgs) {
        var i = 0;
        while(true){
            var testName = "new (" + i + ")";
            var match = false;
            $.each(orgs, function(index, org) {
                if(org.name == testName){
                    match = true;
                }
            });
            if(!match){
                return testName;
            }else{
                i++;
            }
        }
    }
    

    var getParentKeys = function(node){
        var parentKeys = [];
        // get the parents
        if(node.getLevel() > 1){
            node.visitParents(function(node) { 
	            if(node.data.key != "_1"){
	                parentKeys.push(node.data.key);
	            }
            }); 
        }
        parentKeys.reverse();

        var parentsForUrl = "";
        $.each(parentKeys, function(index, parentKey) {
             parentsForUrl = parentsForUrl + "&parentId=" + parentKey;
        });
        return parentKeys;
    }

    
    var bindContextMenu = function(span){
        // Add context menu to this node:
        $(span).contextMenu({menu: "myMenu"}, function(action, el, pos) {
            // The event was bound to the <span> tag, but the node object
            // is stored in the parent <li> tag
            var node = $.ui.dynatree.getNode(el);
            //console.log("context menu... node.data.title: "  + node.data.title);
            switch( action ) {
            case "addChild" :
                console.log("addChild");
                orgsTreeControllerThis.addChild(node);
                break;
            case "addSibling" :
                console.log("addSibling");
                orgsTreeControllerThis.addSibling(node);
                break;
            case "edit" :
                console.log("edit");
                orgsTreeControllerThis.orgClickHandler(node);
                break;
            case "delete" :
                console.log("delete");
                if(!confirm("Confirm deletion?")){
                    return;
                }
                var parentNode = orgsTreeControllerThis.getParentNode(node);
                var options = {
                      'success': function () { 
                          onDelete(parentNode);
                      },
                      'type' : "POST",
                      'data' : "&tree=" + node.data.key
                    };
                $(document).trigger('orgsDisable');
                new AjaxHelper(options).getJSON("/orgDelete.app");
                break;
            case "quit" :
                console.log("quit");
                break;
            case "cut":
            case "copy":
            case "paste":
            default:
                alert("Whoops... that action is not implemented yet.");
            }
        });
    }
    var showTreeDisplayError = function(){
    	$("#org_cannot_show_tree_error_message_container").show();
    	$("#org_container_id").hide();
    	$("#tree").hide();
        $(document).trigger('orgsDisable');
    }

    var showTreeUpdateError = function(){
    	$("#org_cannot_update_tree_error_message_container").show();
    	$("#org_container_id").hide();
    	$("#tree").hide();
        $(document).trigger('orgsDisable');
    }
    
    //this.tree = treeDiv.dynatree("getTree");
    
    if(typeof OrgsTreeController.prototype.getOrg == "undefined"){
    	
        OrgsTreeController.prototype.loadTree = function(orgs) {
        	this.orgs = orgs;
            // get the top level orgs, those with null parentOrg
            var childNodes = [];
            $.each(this.orgs, function(index, org) {
                if(org.parentOrg == undefined){
                    childNodes.push({title: org.name, 
                        isLazy: true, 
                        key: org.id, 
                        isFolder: true, 
                        desc: org.desc,
                        tooltip: "Click to view the details. Drag and drop to move to another location in the tree. Check for deletion."
                     });
                }
            });
            
        	var treeDiv = $("#tree").dynatree({
                // "this" in these callbacks == the tree 
                onActivate: function(node) {
                    if(this.isUserEvent()){ 
                        // The user clicked on a node.
                        // Generate a history event. This will cause
                        // the redisplay of the list and / or the detail.
                        orgsTreeControllerThis.orgClickHandler(node);
                    }
                    //$("#dynatree-id-" + node.data.key).addClass("fap_dynatree_active");
                },
                onCreate: function(node, span){
                    bindContextMenu(span);
                },
                onLazyRead: function(node){
                    // lazy read does not do an Ajax call... it retrieves from the orgs array
                    var childNodes = [];
                    $.each(orgsTreeControllerThis.orgs, function(index, org) {
                        if(org.parentOrg == undefined){
                           // ignore
                        }else{
                            if(org.parentOrg.id == node.data.key){
                                childNodes.push({title: org.name,
                                    isLazy: true, 
                                    key: org.id, 
                                    isFolder: true, 
                                    desc: org.desc,
                                    tooltip: "Click to view the details. Drag and drop to move to another location in the tree. Check for deletion."});
                            }
                        }
                    });
                    node.setLazyNodeStatus(DTNodeStatus_Ok);
                    node.addChild(childNodes);
                },
                onPostInit: function(isReloading, isError) {
                    orgsTreeControllerThis.treeVersion = new Date().getTime();
                 },
                 onSelect: function(flag, node){
                     // a check on a parent should cause all children to be checked
                     if(flag){
                         node.visit(function(childNode){
                             childNode.select(true);
                         },false);
                     }
                 },
                persist: false,
                children: childNodes,
                generateIds: true,
                checkbox : true,
                dnd: {
                      onDragStart: function(node) {
                        /** This function MUST be defined to enable dragging for the tree.
                         *  Return false to cancel dragging of node.
                         */
                          console.log("onDragStart " + node.data.key);
                          return true;
                      },
                      onDragStop: function(node) {
                          console.log("onDragStop " + node.data.key);
                      },
                      autoExpandMS: 1000,
                      preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
                      onDragEnter: function(node, sourceNode) {
                          return true;
                      },
                      onDragOver: function(node, sourceNode, hitMode) {
                          //console.log("onDragOver node: " + node.data.key + ", sourceNode: " + sourceNode.data.key + ", hitMode: " + hitMode+ ", node.isDescendantOf(sourceNode):" + node.isDescendantOf(sourceNode));
                          // Prevent dropping a parent below it's own child
                          // this just causes the visual stop sign.. they can still drop, though!
                          if(node.isDescendantOf(sourceNode)){
                              return false;
                          }
                      },
                      onDrop: function(node, sourceNode, hitMode, ui, draggable) {
                          var nodeOrg = orgsTreeControllerThis.getOrg(node.data.key);
                          var sourceNodeOrg = orgsTreeControllerThis.getOrg(sourceNode.data.key);
                          console.log("onDrop... hitMode:  " + hitMode);
                          console.log("onDrop node:" + JSON.stringify(nodeOrg)); 
                          console.log("onDrop sourceNode:" + JSON.stringify(sourceNodeOrg)); 
                          
                          if(node.isDescendantOf(sourceNode)){
                              return false;
                          }
                          if(!confirm("Confirm move?")){
                              return;
                          }
                          var oldParentNode = orgsTreeControllerThis.getParentNode(sourceNode);
                          
                          // execute the move in the truee
                          sourceNode.move(node, hitMode);
                          
                          // might have a new parent now, unless the move was just a resort within a branch
                          var newParentNode = orgsTreeControllerThis.getParentNode(sourceNode);
                          
                          if(newParentNode.data.key == oldParentNode.data.key ){
                              // no change!
                          }else{
                              // update the entry for the org in the orgs array
                              if(newParentNode.getLevel() > 1){
                                  // have a new parent
                                  var newParentOrg = orgsTreeControllerThis.getOrg(newParentNode.data.key);
                                  nodeOrg.parentOrg = newParentOrg;
                              }else{
                                  // is new to the top level
                                  nodeOrg.parentOrg = undefined;
                              }
                             
                              // update the datastore
                              $("#message_container_id").empty();
                                var postData = "&id=" + sourceNode.data.key + "&version=" + sourceNodeOrg.version;
                                if(sourceNode.getLevel() > 1){
                                    postData += "&parentOrgId=" + newParentNode.data.key ;
                                }
                                var options = { // need custom failure here... stop if there is an error!
                                    'success': function (data) { 
                                        
                                    },
                                      'type' : "POST",
                                      'data' : postData
                                 };
                                 var jqXHR = new AjaxHelper(options).getJSON("/orgPutUpdateParent.app");

                                 var deferred = $.when( jqXHR).then(function(jqXHR){
        	                         if(jqXHR.error_message && jqXHR.error_message.name){
        	       			        	  switch ( jqXHR.error_message.name ){
        	       			 		  	  case "unknown_entity":
        	       			 		  		  // How would this happen?
        	       			 		  		  // The org may have been deleted before the submit.
        	       					 		  $("#org_unknown_entity_error_message_container").show();
        	       					 		  $("#org_form_id").hide();
        	       			 		  		  break;
        	       			 		  	  case "version_conflict" :
        	       					 		  $("#org_version_error_message_container").show();
        	       					 		  $("#org_form_id").hide();
        	       			 		  		   break;
        	       			 		  	  default :
        	       			 		  		  throw "unknown error_message value: " + jqXHRdAta.error_message;
        	       			        	  }
        	       		        	  }else{                             
        	       		        		  orgsTreeControllerThis.orgClickHandler(sourceNode);    
        	       		        	  }
                                  });
                          }
                      },
                      onDragLeave: function(node, sourceNode) {
                          // Always called if onDragEnter was called.
                          //console.log("onDragLeave node: " + node.data.key + ", sourceNode: " + sourceNode.data.key);
                      }
                 }
            });   
        	
        	

        }
    	

        OrgsTreeController.prototype.getOrg = function(id) {
            var returnOrg = null;
             $.each(this.orgs, function(index, org) {
                    if(org.id == id){
                        returnOrg = org;
                        return false;
                    }
                });
            if(returnOrg == null){
                throw "cannot find org with i: " + id;
            }
            return returnOrg;
        }
        
        OrgsTreeController.prototype.getParentNode = function(node){
            var parentNode = null;
            node.visitParents(function(node) { 
                parentNode = node;
                return false;
            });
            return parentNode;
        }
        
        OrgsTreeController.prototype.uncheckAll = function(){
        	$("#tree").dynatree("getTree").getRoot().visit(function(childNode){
                childNode.select(false);
            },false);
        }
        
        OrgsTreeController.prototype.addChild = function(nodeIn) {
            if(!this instanceof OrgsTreeController){
                throw "addChild has unexpected value for this keyword";
            }
            var node = null;
            if(typeof nodeIn == "undefined"){
                node = $("#tree").dynatree("getTree").getActiveNode(); // is the parent
            }else{
                node = nodeIn;
            }
            if(node == null){
                alert("Please select a parent.");
                return;
            }
            node.expand(true);
            $("#message_container_id").empty();
            $(document).trigger('orgsDisable');
            var options = {
                'success': function (data) { 
                     console.log("add success...." + JSON.stringify(data));
                	 if(data.error_message && data.error_message.name){
  			        	  switch ( data.error_message.name ){
  			 		  	  case "duplicate_field":
  			 		  		  showTreeUpdateError();
  			 		  		  break;
  			 		  	  default :
  			 		  		  throw "unknown error_message value: " + jqXHRdAta.error_message;
  			        	  }
  		        	  }else{                             
  	                       var childNode = node.addChild({
  	                          key : data.org.id,
  	                          title: data.org.name,
  	                          isFolder: true
  	                      });
  	                      $(document).trigger('orgsEnable');
  	                      orgsTreeControllerThis.orgs.push(data.org);
  	                      orgsTreeControllerThis.orgClickHandler(childNode);       		        	  
  	                  }
                  },
                  'type' : "POST",
                  'data' : "&parentOrgId=" + node.data.key + "&name=" +  getUniqueName(this.orgs) + "&desc=testy"
             };
             var jqXHR = new AjaxHelper(options).getJSON("/orgPutCreate.app");

             var deferred = $.when( jqXHR).then(function(){
                 // nothing here right now
             });
        }
        
        OrgsTreeController.prototype.addSibling = function(activeNode){
            if(!this instanceof OrgsTreeController){
                throw "addChild has unexpected value for this keyword";
            }
            if(typeof activeNode == "undefined"){
                activeNode = $("#tree").dynatree("getTree").getActiveNode(); // is the parent
            }
            var parentNode = {};
            if(activeNode == null){
                parentNode = this.tree.getRoot();
            }else{
                activeNode.visitParents(function(node) { 
                    parentNode = node;
                    return false;
                });
            }
            console.log("parentNode.data.key: " + parentNode.data.key + ", parentNode.getLevel(): " + parentNode.getLevel());
            var httpParams = {};
            // only want to load parentOrgId for children, not the top level orgs
            if(activeNode != null && activeNode.getLevel() > 1){
                httpParams = "&parentOrgId=" + parentNode.data.key + "&name=" +  getUniqueName(this.orgs) + "&desc=testy"
             }else{
               httpParams = "&name=" +  getUniqueName(this.orgs) + "&desc=testy"
             }
             
             $("#message_container_id").empty();
             $(document).trigger('orgsDisable');
             var options = {
                 'success': function (data) { 
                	 
                	 if(data.error_message && data.error_message.name){
  			        	  switch ( data.error_message.name ){
  			 		  	  case "duplicate_field":
  			 		  		  showTreeUpdateError();
  			 		  		  break;
  			 		  	  default :
  			 		  		  throw "unknown error_message value: " + jqXHRdAta.error_message;
  			        	  }
  		        	  }else{                             
  	                       var childNode = parentNode.addChild({
  	                          key : data.org.id,
  	                          title: data.org.name,
  	                          isFolder: true
  	                      });
  	                      $(document).trigger('orgsEnable');
  	                      orgsTreeControllerThis.orgs.push(data.org);
  	                      orgsTreeControllerThis.orgClickHandler(childNode);       		        	  
  	                  }
                 },
                 'type' : "POST",
                 'data' : httpParams
              };

             var jqXHR = new AjaxHelper(options).getJSON("/orgPutCreate.app");

             var deferred = $.when( jqXHR).then(function(){
                 //
              });
        }

        OrgsTreeController.prototype.deleteClickHandler = function(){
            var serializedArray = $("#tree").dynatree("getTree").serializeArray();           
            if(serializedArray.length == 0){
                alert("Please select one or more organizations to be deleted.");
                return;
            }else{
                if(!confirm("Confirm deletions?")){
                    return;
                }
                var parentNode = null;
                if(serializedArray.length == 1){
                    var node = orgsTreeControllerThis.getNode(serializedArray[0].value);
                    if(node == null){
                        // do nothing
                    }else{
                        parentNode = orgsTreeControllerThis.getParentNode(node);
                    }
                }
          
                var options = {
                      'success': function () { 
                          onDelete(parentNode);
                      },
                      'type' : "POST",
                      'data' : $.param(serializedArray)
                    };
                $(document).trigger('orgsDisable');
                new AjaxHelper(options).getJSON("/orgDelete.app");
            }
        }

        OrgsTreeController.prototype.uncheckAllClickHandler = function(){
            this.uncheckAll();
        }
        OrgsTreeController.prototype.orgClickHandler = function(node, refresh){
            var parentKeys = getParentKeys(node);
            if(refresh){
                homeController.orgClickHandler(node.data.key, node.data.title, 0, parentKeys);
            }else{
                homeController.orgClickHandler(node.data.key, node.data.title, orgsTreeControllerThis.treeVersion, parentKeys);
            }
        }
        OrgsTreeController.prototype.getNode = function(key){
            var targetNode = $("#tree").dynatree("getTree").getNodeByKey(key);
            if(targetNode == null){
                throw "cannot get node as the key cannot be found: " + key;
            }
            return targetNode;
        }
        OrgsTreeController.prototype.activateFirst = function(){
            var root = $("#tree").dynatree("getTree").getRoot();
            var children = root.getChildren();
            if(children != null){
                var firstChild = children[0];
                firstChild.activate();
                return firstChild;
            }
        }
        
        OrgsTreeController.prototype.activate = function(id, parentKeys){
            if(!this instanceof OrgsTreeController){
                throw "activate has unexpected value for this keyword";
            }
            var targetNode = $("#tree").dynatree("getTree").getNodeByKey(id);
            if(targetNode == null){
                if(parentKeys.length > 0 ){
                     $.each(parentKeys, function(index, parentKey) {
                         var parentNode = $("#tree").dynatree("getTree").getNodeByKey(parentKey);
                         if(parentNode == null){
                             console.log("cannot redisplay tree as the parent node can't be found. parentKey: " + parentKey);
                             return false;
                         }else{
                             if(!parentNode.isExpanded()){
                                 parentNode.expand(true);
                             }
                         }
                     });
                     targetNode = $("#tree").dynatree("getTree").getNodeByKey(id);
                     if(targetNode == null){
                         console.log("cannot find target node even after expanding all parents. id: " + id);  
                         showTreeDisplayError();
                         return;
                     }
                }else{
                     console.log("cannot find target node and there are no parents. id: " + id);
                     showTreeDisplayError();
                     return;
                }
            }
            targetNode.activate();
            return targetNode;
        }
    }
}